package main;

import aux.*;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
 *
 * @author YanCouto
 * @version 0.1
 */
public class BallGame {


    JFrame f;
    ArrayList<Scorer> sco;
    public static Color shotColor,ballColor,goodShotColor;
    File fconfig;
    public static BallGame BG = new BallGame();
    Difficulty d;
    Doable checker;
    public static WelcomeFrame wf;
    LooserFrame lf;
    int x,y,r, lives, goodShotsEaten;
    PaintArea p;
    Thread rai,sh,rp, sc,gs,hs;
    ArrayList<Shot> normalShots;
    ArrayList<GoodShot> goodShots;
    ArrayList<Heart> hearts;
    Ball ball;
    Random ra;
    int time;
    long score;
    String name = null;
    boolean isLooser, done,conditionsMet, permanentlyLost, isChallenge;
    boolean canMoveXAxis,canMoveYAxis,shootHearts, firstTime;
    int maxgoodshots,maxshots,maxradius,minyspeed,maxyspeed,minxspeed,maxxspeed, shotssize;


    public static void main(String[] args) throws Exception{   
            BG.checkFiles();
            BG.doAll();
    }
    
    public void doAll() {
        if(name==null)
            name = JOptionPane.showInputDialog(null,"What's your name?","Please do tell", JOptionPane.QUESTION_MESSAGE);
        wf = new WelcomeFrame();
        wf.doStuff();
        d = wf.d;
        System.out.println(d);
        wf.setVisible(false);
        wf.dispose();
        if(d==null)
            close();
        
        mkFiles();

        f = new JFrame("Ball Game!");
        f.setSize(600,600);
        f.setResizable(false);
        f.setLocationRelativeTo(null);
        
        
        lives = d.lives();
        sh = new NormalShooter();
        sc = new ScoreCounter();
        rp = new Repainter();
        ra = new Random();
        gs = new GoodShooter();
        hs = new HeartShooter();
        normalShots = new ArrayList<Shot>();
        goodShots = new ArrayList<GoodShot>();
        hearts = new ArrayList<Heart>();
        p = new PaintArea();
        rai = new Raiser();

        f.add(p);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }

    public BallGame () {
        super();
        init();
    }
    
    private void startAll() {
        rp.start();
        rai.start();
        sh.start();
        sc.start();
        gs.start();
        hs.start();
    }
    


    public void checkFiles() {
        try {
        if(!new File("config").exists()) {
            new File("config").mkdir();
            fconfig.createNewFile();
        }
        else {
            ObjectInputStream oi = new ObjectInputStream(new FileInputStream(fconfig));
            name = (String) oi.readObject();
            shotColor = (Color) oi.readObject();
            goodShotColor = (Color) oi.readObject();
            ballColor = (Color) oi.readObject();
        }
     } catch (Exception ex) {}
                
                
        for(Difficulty d1 : Difficulty.values()) {
            if(d1 != Difficulty.CUSTOM) {
                String fileName = "scores/" + d1.name().toLowerCase() + ".dat";
            File scorr = new File(fileName);
            if(!scorr.exists())
                writeScorers(scorr,new ArrayList<Scorer>());
            }
        }
    }

    private void init() {
        maxgoodshots = maxshots = maxradius = -1;
        isLooser = conditionsMet = done = permanentlyLost = isChallenge = false;
        canMoveXAxis = canMoveYAxis = firstTime = shootHearts = true;
        minyspeed = minxspeed = 1;
        maxyspeed = maxxspeed = 5;
        shotssize = 3;
        x=y=300;
        r=5;
        score = time = goodShotsEaten = 0;
        checker = null;
        fconfig = new File("config/config.conf");
        ball = new Ball();
    }

    private void mkFiles() {
            if(ballColor==null)
                ballColor = Color.RED;
            if(shotColor==null)
                shotColor=Color.BLACK;
            if(goodShotColor==null)
                goodShotColor= new Color(0, 170, 0);
            float[] c = new float[4];
        
        try {
            ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(fconfig,false));
            oo.writeObject(name);            
            oo.writeObject(shotColor);
            oo.writeObject(goodShotColor);
            oo.writeObject(ballColor);
        } catch (Exception ex) {}
    }

    public class PaintArea extends JComponent {
        

        public PaintArea () {
            this.addMouseMotionListener(new Mouser ());
        }
        @Override
        public void paint (Graphics g) {

            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setColor(Color.LIGHT_GRAY);
            for(int i=0;i<=f.getWidth();i+=10)
                g2.drawLine(i, 0, i, f.getHeight());
            for(int i=0;i<=f.getHeight();i+=10)
                g2.drawLine(0, i, f.getWidth(), i);
            
                        if(firstTime) {
                Font normal = g2.getFont();
                g2.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 30));
                g2.setColor(Color.RED);
                g2.drawString("Move to begin", 200, 290);
                g2.setFont(normal);
            }
            
            ball.set(x-(r/2),y-(r/2),r);


            if(conditionsMet) {
                Font normal = g2.getFont();
                g2.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 30));
                g2.setColor(Color.RED);
                g2.drawString("CHALLENGE COMPLETED!", 50,130);
                g2.setFont(normal);
            }
            

            g2.setColor(ballColor);
            if(ball.isDead() && !isLooser) {
                isLooser = true;
                if(isChallenge)
                    permanentlyLost = true;
                else 
                    YouLose();
            }
            g2.fill(ball);
            g2.setColor(shotColor);
            Shot[] aux = new Shot[normalShots.size()];
            aux = normalShots.toArray(aux);
            for(Shot shot : aux) {
                shot.move();
                g2.fill(shot);
            }
            ball.check();
            g2.setColor(goodShotColor);
            GoodShot[] gs = new GoodShot[goodShots.size()];
            gs = goodShots.toArray(gs);
            for(GoodShot g1 : gs) {
                g1.move();
                g2.fill(g1);
            }
            g2.setColor(Color.RED);
            
            Heart[] h = new Heart[hearts.size()];
            h= hearts.toArray(h);
                for (Heart h1 : h) {
                    h1.paint(g2);
                }    

            g2.setColor(Color.BLACK);
            final int auxx = f.getWidth()-150;
            g2.drawString("Shots in-screen: " + normalShots.size(), auxx,20);
            g2.drawString("Radius of the Ball: " + r, auxx,40);
            g2.drawString("Score: " + score, auxx,60);
            g2.drawString("Time Playing: " + time, auxx,80);
            g2.drawString("Lives: " + lives,auxx, 100);
            
            
        }
    }

    public final void close() {
        System.exit(0);
    }


        private class Mouser implements MouseMotionListener {

        @Override
            public void mouseDragged(MouseEvent e) {
                if(canMoveXAxis) x = e.getX();
                if(canMoveYAxis) y = e.getY();
                if(firstTime) {
                    startAll();
                    firstTime=false;
                }
            }

        @Override
            public void mouseMoved(MouseEvent e) {
                if(canMoveXAxis) x = e.getX();
                if(canMoveYAxis) y = e.getY();
                if(firstTime) {
                    startAll();
                    firstTime=false;
                }
            }
        }

    private class Raiser extends Thread {
        @Override
        public void run () {
                while(!done) {
                    if((maxradius!=-1 && r>=maxradius))
                        break;
                try {
                    r++;
                    sleep(d.timeToRadiusIncrease());
                } catch (InterruptedException ex) {}
                }
        }
    }
    

    private class Shot extends Rectangle2D.Float {
        boolean up,right;
        int xspeed, yspeed;
        public Shot (int x, int y) {
            this(x,y,shotssize);
        }
        protected Shot (int x,int y, int r) {
            super(x,y,r,r);
            up = ra.nextBoolean();
            right = ra.nextBoolean();
            xspeed = NumberPro.random(minxspeed, maxxspeed);
            yspeed = NumberPro.random(minyspeed, maxyspeed);
        }
        void move () {
            boolean c1 = this.getBounds2D().getMinX()<=0;
            boolean c2 = this.getBounds2D().getMaxX()>=f.getWidth();
            boolean c3 = this.getBounds2D().getMinY()<=0;
            boolean c4 = this.getBounds2D().getMaxY()>=f.getHeight()-30;

            if(c1) right = true;
            if(c2) right = false;
            if(c3) up = false;
            if(c4) up=true;

            if(up) super.y-=yspeed;
            else super.y+=yspeed;
            if(right) super.x+=xspeed;
            else super.x-=xspeed;
        }
    }
    
   private class GoodShot extends Shot {
       public GoodShot (int x,int y) {
           super(x,y,6);
       }
   }
   
   public final void completeChallenge () {
       conditionsMet = true;
   }

    public class Repainter extends Thread {
        @Override
        public void run () {
                while(!done) {
                try {
                    p.repaint();
                    if(checker!=null)
                        checker.doStuff();
                    sleep(60);
                } catch (InterruptedException ex) {}
                }
        }
    }

    public class NormalShooter extends Thread {
        @Override
        public void run () {
            while(!done) {
                Point2D aux = getCoordinates ();
                normalShots.add(new Shot((int) aux.getX(),(int) aux.getY()));
                if(maxshots!=-1 && normalShots.size()>=maxshots)
                    break;
                try {
                    sleep(d.timeToShoot());
                } catch (InterruptedException ex) {}
            }
        }
    }
    
    public class GoodShooter extends Thread {
        @Override
        public void run () {
            if(d.timeToGoodShot()!=-1) {
            while(!done) {
                if(maxgoodshots!=-1 && goodShots.size()>=maxgoodshots)
                    break;
                Point2D aux = getCoordinates ();
                goodShots.add(new GoodShot((int) aux.getX(),(int) aux.getY()));
                try {
                    sleep(d.timeToGoodShot());
                } catch (InterruptedException ex) {}
            }
            }
        }
    }
    
    public class HeartShooter extends Thread {
        @Override
        public void run () {
            if(shootHearts && d.timeToHeart()!=-1) {
            while(!done) {
                if(lives<d.lives()) {
                Point2D aux = getCoordinates ();
                Heart h = new Heart((int) aux.getX(),(int) aux.getY(),12);
                hearts.add(h);
                }
                try {
                    sleep(d.timeToHeart());
                } catch (InterruptedException ex) {}
                }
            }
            }
        }
    
    public Point2D getCoordinates () {
                int side = ra.nextInt(4) +1;
                int pos;
                Point2D p2 = null;
                if(side==1) {
                    pos = ra.nextInt(f.getWidth()-1) +1;
                    p2 = new Point2D.Double(pos,0);
                }
                if(side==3) {
                    pos = ra.nextInt(f.getWidth()-1) +1;
                    p2 = new Point2D.Double(pos,f.getHeight());
                }
                if(side==2) {
                    pos = ra.nextInt(f.getHeight()-1) +1;
                    p2 = new Point2D.Double(f.getWidth(),pos);
                }
                if(side==4) {
                    pos = ra.nextInt(f.getHeight()-1) +1;
                    p2 = new Point2D.Double(0,pos);
                }
                return p2;
    }
    
    

    public class ScoreCounter extends Thread{
        @Override
        public void run () {
            while(!done) {
                try {
                    time++;
                    score+= ((time^2)/3)*(r + normalShots.size());
                    sleep(1000);
                } catch (InterruptedException ex) {}
            }
        }
    }


        public synchronized void YouLose() {
            done = true;
            
            lf = new LooserFrame();
            lf.setVisible(true);
        }

        public void restart () {
            this.f.dispose();
            BG = new BallGame();
            BG.doAll();
        }

    public class Ball extends Ellipse2D.Float {

        public Ball () {
            super(0,0,0,0);
        }
        
        public void set(int x,int y,int r) {
            this.x = x;
            this.y = y;
            this.height = this.width = r;
        }

        public boolean isDead () {
            boolean dead = false;
            Shot[] aux = new Shot[normalShots.size()];
            aux = normalShots.toArray(aux);
            for (Shot shot : aux) {
                if(this.intersects(shot)) {
                    if(lives==0)
                        dead=true;
                    else{
                        lives--;
                        normalShots.remove(shot);
                    }
                }
            }
            boolean c1 = getBounds2D().getMinX()<=0;
            boolean c2 = getBounds2D().getMaxX()>=f.getWidth();
            boolean c3 = getBounds2D().getMinY()<=0;
            boolean c4 = getBounds2D().getMaxY()>=f.getHeight()-30;
            
            if(c1 || c2 || c3 || c4)
                dead=true;
            
            return dead;
        }
        
        public void check () {
            GoodShot[] gs = new GoodShot[goodShots.size()];
            gs = goodShots.toArray(gs);
            for(GoodShot g : gs) {
                if(this.intersects(g)){
                    goodShots.remove(g);
                    r-=5;
                    goodShotsEaten++;
                }
            }
            Heart[] h = new Heart[hearts.size()];
            h = hearts.toArray(h);
            for(Heart h1: h) {
                if(this.intersects(h1.base.getBounds2D())){
                    hearts.remove(h1);
                    lives++;
                }
            }
        }
    }
    
    public class Heart {
        int x,y,yspeed,xspeed,size;
        int[] xx,yy;
        boolean up,right;
        Rectangle2D bounds;
        Polygon base;
        Arc2D.Double top1,top2;
        Shape[] shapes;
        public Heart (int x,int y,int size) {
            this.x = x;
            this.y = y;
            this.size = size;
            bounds  = new Rectangle2D.Double(x,y,size,size);
            int[] xxx = {x,x+size/2,x+size};
            int[] yyy = {y+size/4,y+size,y+size/4};
            xx = xxx;
            yy = yyy;
            base = new Polygon(xx,yy,xx.length);
            top1 = new Arc2D.Double(x,y,size/2,size/2,0,180,Arc2D.CHORD);
            top2 = new Arc2D.Double(x+size/2,y, size/2, size/2, 0, 180,Arc2D.CHORD);
            shapes = new Shape[3];
            shapes[0] = top1;
            shapes[1] = top2;
            shapes[2] = base;
            xspeed = ra.nextInt(5) +1;
            yspeed = ra.nextInt(5) +1;
            up = ra.nextBoolean();
            right = ra.nextBoolean();
        }   
        private void move () {
            boolean c1 = bounds.getMinX()<=0;
            boolean c2 = bounds.getMaxX()>=f.getWidth();
            boolean c3 = bounds.getMinY()<=0;
            boolean c4 = bounds.getMaxY()>=f.getHeight();

            if(c1) right = true;
            if(c2) right = false;
            if(c3) up = false;
            if(c4) up = true;
            

            
            if(up) {
                y-=yspeed;
                top1.y -= yspeed;
                top2.y -= yspeed;
                yy = addInAll(yy,-yspeed);
            }
            else {
                y-=yspeed;
                top1.y += yspeed;
                top2.y += yspeed;
                yy= addInAll(yy,yspeed);
            }
            if(right) {
                x+=xspeed;
                top1.x += xspeed;
                top2.x += xspeed;
                xx = addInAll(xx,xspeed);
            }
            else {
                x-=xspeed;
                top1.x -= xspeed;
                top2.x -= xspeed;
                xx = addInAll(xx,-xspeed);
            }
            base.npoints = xx.length;
            base.xpoints = xx;
            base.ypoints = yy;
            bounds.setFrame(x, y, size, size);
        }
        
        public void paint(Graphics2D g2) {
            this.move();
            for(Shape s: shapes) {
                g2.fill(s);
            }
        }
        
        private int[] addInAll(int[] array,int num) {
            int[] aux = new int[array.length];
            for(int i=0;i<array.length;i++) {
                aux[i] = array[i]+num;
            }
            return aux;
        }
    }

    public static class Scorer implements Serializable,Comparable<Scorer> {

        String name;
        int score;

        public Scorer (String n,int s) {
            name = n;
            score = s;
        }

        @Override
        public String toString() {
            return (name + " - " + score);
        }

        @Override
        public int compareTo(Scorer o) {
            return (int) Math.signum(o.score-this.score);
        }
    }

    private ArrayList<Scorer> getScorers(File f) {
        ArrayList<Scorer> ssc = new ArrayList<Scorer>();
      if(f.exists()) {
          try {
              ssc = (ArrayList<Scorer>) (new ObjectInputStream(new FileInputStream(f))).readObject();
          } catch (Exception e) { e.printStackTrace(); }
      }
      else{
            try {
                f.mkdirs();
                f.delete();
                f.createNewFile();
            } catch (IOException ex) {}
      }
        return ssc;
    }

    public void writeScore () {
        String f = "scores/";
        f+= d.name().toLowerCase() + ".dat";
        sco = getScorers(new File(f));
        sco.add(new Scorer(name,(int) score));
        writeScorers(new File(f),sco);
    }

    private void writeScorers(File f,ArrayList<Scorer> asc) {
        if(!f.exists()) {
            try {
                f.mkdirs();
                f.delete();
                f.createNewFile();
            } catch (IOException ex){ ex.printStackTrace(); }
        }
        Collections.sort(asc);
        //if(asc==null) System.out.println("WTFFFFFFFFFFFFFFFFF");
        ObjectOutputStream oo;
        try {
            oo = new ObjectOutputStream(new FileOutputStream(f,false));
            oo.writeObject(asc);
            oo.close(); 
        } catch (IOException ex) {
            Logger.getLogger(BallGame.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}